package step

import (
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/wanchain/go-mpc/log"
	"github.com/wanchain/go-mpc/p2p/discover"
	"github.com/wanchain/go-mpc/storeman/osmconf"
	mpcprotocol "github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
	"math/big"
	"strconv"
)

type mpcPointGenerator struct {
	BaseGenerator
	seed        mpcprotocol.CurvePointer
	message     map[discover.NodeID]mpcprotocol.CurvePointer
	result      mpcprotocol.CurvePointer
	preValueKey string
	grpIdString string
	smcer       mpcprotocol.SchnorrMPCer
	ContextId   uint64
}

func createPointGenerator(preValueKey string) *mpcPointGenerator {
	return &mpcPointGenerator{message: make(map[discover.NodeID]mpcprotocol.CurvePointer), preValueKey: preValueKey}
}

func (point *mpcPointGenerator) initialize(totalNum int, result mpcprotocol.MpcResultInterface) error {
	point.SetContextId(result.GetMpcContextId())
	log.SyslogDebug("mpcPointGenerator.initialize begin ", "ctx", point.GetContextId())

	// get self rpkshare
	_, grpIdString, _ := osmconf.GetGrpId(result)

	point.grpIdString = grpIdString

	selfIndex, err := osmconf.GetOsmConf().GetSelfInx(grpIdString)
	if err != nil {
		log.SyslogErr("mpcPointGenerator", "initialize", err, "ctx", point.GetContextId())
		return err
	}
	key := mpcprotocol.RPkShare + strconv.Itoa(int(selfIndex))
	value, err := result.GetByteValue(key)

	log.SyslogDebug("public share mpcPointGenerator.initialize GetValue ",
		"key", point.preValueKey,
		"RPKShare ", hex.EncodeToString(value), "ctx", point.GetContextId())

	if err != nil {
		log.SyslogErr("mpcPointGenerator.initialize get preValueKey fail", "ctx", point.GetContextId())
		return err
	}

	point.seed, err = point.smcer.UnMarshPt(value)
	if err != nil {
		log.SyslogErr("mpcPointGenerator", "UnMarshPt", err.Error(), "ctx", point.GetContextId())
		return err
	}

	log.SyslogDebug("mpcPointGenerator.initialize succeed", "ctx", point.GetContextId())
	return nil
}

func (point *mpcPointGenerator) calculateResult() error {
	log.SyslogDebug("mpcPointGenerator.calculateResult begin", "ctx", point.GetContextId())
	var recieveIndex []uint16

	seeds := make([]big.Int, 0)
	gpkshares := make([]mpcprotocol.CurvePointer, 0)
	for nodeId, value := range point.message {
		xValue, err := osmconf.GetOsmConf().GetXValueByNodeId(point.grpIdString, &nodeId, point.smcer)
		if err != nil {
			log.SyslogErr("mpcPointGenerator", "calculateResult", err.Error(), "ctx", point.GetContextId())
			return err
		}
		seeds = append(seeds, *xValue)
		gpkshares = append(gpkshares, value)

		tempIndex, err := osmconf.GetOsmConf().GetInxByNodeId(point.grpIdString, &nodeId)
		recieveIndex = append(recieveIndex, tempIndex)
	}

	for index, gpkshareTemp := range gpkshares {
		log.SyslogDebug("all public share",
			"gpk share ", point.smcer.PtToHexString(gpkshareTemp),
			"seed", hex.EncodeToString(seeds[index].Bytes()), "ctx", point.GetContextId())
	}

	threshold, _ := osmconf.GetOsmConf().GetThresholdNum(point.grpIdString)
	if threshold < uint16(1) {
		log.SyslogErr("threshold is lesser 1", "ctx", point.GetContextId())
		return errors.New("threshold is lesser 1")
	}
	degree := threshold - 1

	allIndex, _ := osmconf.GetOsmConf().GetGrpElemsInxes(point.grpIdString)

	losingIndex := osmconf.Difference(*allIndex, recieveIndex)

	// lagrangeEcc
	log.SyslogInfo("all public",
		"Need nodes number:", threshold,
		"Now nodes number:", len(gpkshares),
		"losing nodes", osmconf.GetOsmConf().BuildLosingByIndex(point.grpIdString, losingIndex), "ctx", point.GetContextId())
	if len(gpkshares) < int(threshold) {
		//since now, system only slash the malice action, No work is not a malice action.
		//return mpcprotocol.ErrRNW
		return errors.New(fmt.Sprintf("%s %s", mpcprotocol.ErrTooLessDataCollected.Error(), osmconf.GetOsmConf().BuildLosingByIndex(point.grpIdString, losingIndex)))
	}

	smpcer := point.smcer
	result := smpcer.LagrangeECC(gpkshares, seeds[:], int(degree))

	if !smpcer.IsOnCurve(result) {
		log.SyslogErr("mpcPointGenerator::calculateResult", "mpcPointGenerator.ValidatePublicKey fail. err", mpcprotocol.ErrPointZero.Error(), "ctx", point.GetContextId())
		return mpcprotocol.ErrPointZero
	}

	point.result = result

	log.SyslogDebug("gpk mpcPointGenerator.calculateResult succeed ",
		"gpk ", smpcer.PtToHexString(result), "ctx", point.GetContextId())
	return nil
}

func (point *mpcPointGenerator) SetSchnorrMpcer(smcer mpcprotocol.SchnorrMPCer) error {
	point.smcer = smcer
	return nil
}

func (point *mpcPointGenerator) SetContextId(contextId uint64) {
	point.ContextId = contextId
}

func (point *mpcPointGenerator) GetContextId() uint64 {
	return point.ContextId
}

func (point *mpcPointGenerator) SetMpcContext(mpc MpcContextFunc) {
	point.mpc = mpc
}

func (point *mpcPointGenerator) GetMpcContext() MpcContextFunc {
	return point.mpc
}
